﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Text.RegularExpressions;

namespace CleanSVG
{
	public abstract class SVGElementCleaners
	{
		//	coord = (?<mx>[+\-]?(?:\d+|\d*\.\d+))
		//	sep (?:\s*,\s*|\s+)

		//  d="M238.5 684 L238.5 787.5 L0 792"
		static readonly Regex rxPath = new Regex(@"<path\s+[^>]+?/>", RegexOptions.Compiled);
		//	the expression below doesn't require an "L" command but if the path is valid, there'll be at least one and we don't match any other command values
		static readonly Regex rxLinearPathData = new Regex(@"\s+d\s*=\s*""M\s*(?<mx>[+\-]?(?:\d*\.\d+|\d+))(?:\s*,\s*|\s+)(?<my>[+\-]?(?:\d*\.\d+|\d+))(?<pts>(?:\s*[L,]\s*|\s+)(?<px>[+\-]?(?:\d*\.\d+|\d+))(?:\s*,\s*|\s+)(?<py>[+\-]?(?:\d*\.\d+|\d+)))+\s*(?<z>Z)?\s*""", RegexOptions.Compiled);

		//	<ellipse cx="36" cy="756" rx="36" ry="36" class="st1"/>
		static readonly Regex rxEllipse = new Regex(@"<ellipse\s+[^>]+?/>", RegexOptions.Compiled);
        static readonly Regex rxRXY = new Regex(@"\s+(?<attr>r[xy])\s*=\s*""(?<val>(?:\+|-)?(?:\d*\.\d+|\d+))""", RegexOptions.Compiled);

	    private static readonly Regex _rxTspanElement = new Regex(@"(?<open><tspan(\s*(?<attr>[\w\-]+)\s*=\s*""(?<value>[^""]*)"")*\s*>)(?<inner>.*?)(?<close></tspan>)",
	                                                     RegexOptions.Singleline | RegexOptions.Compiled);

	    private static readonly Regex _rxClass = new Regex(@"<tspan\s[^>]*?class\s*=\s*""(?<classname>[^""]*)""", RegexOptions.Singleline | RegexOptions.Compiled);

	    public static string EllipsesToCircles(string svg, CleanSVG.StatusMessager messager)
		{
			var replacedEllipses = 0;

			svg = rxEllipse.Replace(svg,
			                         delegate(Match m)
			                             {
			                                 var values = new Dictionary<string, double>();

                                             for (var m2 = rxRXY.Match(m.Value); m2.Success; m2 = m2.NextMatch())
                                                values.Add(m2.Groups["attr"].Value, double.Parse(m2.Groups["val"].Value));

                                             if (values.ContainsKey("rx") && values.ContainsKey("ry") && values["rx"] == values["ry"])
                                             {
                                                 var replacement = Regex.Replace(m.Value, @"^<ellipse\s+", "<circle ");
                                                 replacement = Regex.Replace(replacement, @"\s+rx\s*=\s*", " r=");
                                                 replacement = Regex.Replace(replacement, @"\s+ry\s*=\s*""[^""]+""", string.Empty);

                                             	++replacedEllipses;

                                                 return replacement;
                                             }

			                                 return m.Value;
			                             });

			if (replacedEllipses > 0)
				messager("{0:#,##0} circular ellipse elements replaced with circle elements.", replacedEllipses);

			return svg;
		}

		public static string PathsToPolylines(string svg, CleanSVG.StatusMessager messager)
		{
			var pathsReplacedWithLines = 0;
			var pathsReplacedWithRects = 0;
			var pathsReplacedWithPolygons = 0;
			var pathsReplacedWithPolylines = 0;

			svg = rxPath.Replace(svg,
			                      delegate(Match pathMatch)
			                      	{
			                      		var m = rxLinearPathData.Match(pathMatch.Value);

			                      		if (m.Success && m.Groups["px"].Captures.Count > 0)
			                      		{
			                      			string newTag, newData;

			                      			if (m.Groups["px"].Captures.Count == 1)
			                      			{
			                      				//	replace with line
			                      				newTag = "line";
			                      				newData = string.Format("x1=\"{0}\" y1=\"{1}\" x2=\"{2}\" y2=\"{3}\"",
			                      				                        m.Groups["mx"].Value, m.Groups["my"].Value,
			                      				                        m.Groups["px"].Value,
			                      				                        m.Groups["py"].Value);
			                      				++pathsReplacedWithLines;
			                      			}
			                      			else if (IsRect(m, out newData))
			                      			{
			                      				newTag = "rect";
												++pathsReplacedWithRects;
											}
											else
			                      			{
			                      				//	replace with polygon or polyline
			                      				var isPolygon = string.Compare(m.Groups["z"].Value, "z", true) == 0;
			                      				newTag = "poly" + (isPolygon ? "gon" : "line");

			                      				var pts = new StringBuilder("points=\"");
			                      				pts.AppendFormat("{0},{1}", m.Groups["mx"].Value, m.Groups["my"].Value);
			                      				for (var i = 0; i < m.Groups["px"].Captures.Count; ++i)
			                      					pts.AppendFormat(" {0},{1}", m.Groups["px"].Captures[i].Value, m.Groups["py"].Captures[i].Value);
			                      				pts.Append("\"");
			                      				newData = pts.ToString();

			                      				if (isPolygon)
			                      					++pathsReplacedWithPolygons;
			                      				else
			                      					++pathsReplacedWithPolylines;
			                      			}

											var replacement = pathMatch.Value;
											replacement = Regex.Replace(replacement, @"(?<=^<)path(?=\s+)", newTag);
			                      			replacement = Regex.Replace(replacement, @"(?<=\s+)d\s*=\s*""[^""]+""", newData);
			                      			return replacement;
			                      		}

			                      		return pathMatch.Value;
			                      	});

			if (pathsReplacedWithLines > 0)
				messager("{0:#,##0} path elements replaced with line elements.", pathsReplacedWithLines);

			if (pathsReplacedWithRects > 0)
				messager("{0:#,##0} path elements replaced with rect elements.", pathsReplacedWithRects);

			if (pathsReplacedWithPolygons > 0)
				messager("{0:#,##0} path elements replaced with polygon elements.", pathsReplacedWithPolygons);

			if (pathsReplacedWithPolylines > 0)
				messager("{0:#,##0} path elements replaced with polyline elements.", pathsReplacedWithPolylines);

			return svg;
		}

		private static bool IsRect(Match m, out string newData)
		{
			if (string.Compare(m.Groups["z"].Value, "z", true) == 0 && m.Groups["px"].Captures.Count == 3)
			{
				var c0x = double.Parse(m.Groups["mx"].Value);
				var c0y = double.Parse(m.Groups["my"].Value);
				var c1x = double.Parse(m.Groups["px"].Captures[0].Value);
				var c1y = double.Parse(m.Groups["py"].Captures[0].Value);
				var c2x = double.Parse(m.Groups["px"].Captures[1].Value);
				var c2y = double.Parse(m.Groups["py"].Captures[1].Value);
				var c3x = double.Parse(m.Groups["px"].Captures[2].Value);
				var c3y = double.Parse(m.Groups["py"].Captures[2].Value);

				if (c0x == c1x && c0y == c3y && c1y == c2y && c2x == c3x)
				{
					newData = string.Format("x=\"{0}\" y=\"{1}\" width=\"{2}\" height=\"{3}\"", c0x, c0y, c3x - c0x, c1y - c0y);
					return true;
				}
			}

			newData = null;
			return false;
		}

		public static string ConvertTspans(string svg, string cssStyleRules, CleanSVG.StatusMessager messager)
		{
			var tspansRemoved = 0;
		    messager("Converting tspan elements to text elements...");

			var svgClassMatch = Regex.Match(svg, @"<svg\s[^>]*?class\s*=\s*""(?<classname>[^""]*)""");
			var svgClassName = svgClassMatch.Success ? svgClassMatch.Groups["classname"].Value : string.Empty;

			var rxTextElement = new Regex(@"<text(\s*(?<attr>[:\w\-]+)\s*=\s*""(?<value>[^""]*)"")*\s*>(?<inner>.*?)</text>", RegexOptions.Singleline | RegexOptions.Compiled);

			svg = rxTextElement.Replace(svg,
			                            delegate(Match m)
			                            	{
												var textAttrs = GetAtributesDictionary(m);
												
												//  if text element doesn't have a nested tspan, only convert a simple translation matrix transform to an x and y (Illustrator pattern)
												if (!_rxTspanElement.IsMatch(m.Groups["inner"].Value))
												{
													float translateX, translateY;
													if (GetTranslateXYFromTransform(textAttrs, out translateX, out translateY))
													{
														return Regex.Replace(m.Value, @"\s+transform\s*=\s*""[^""]+""", string.Format(" x=\"{0}\" y=\"{1}\"", translateX, translateY));
													}

													return m.Value;
												}

												if (!textAttrs.ContainsKey("class"))
												{
													var className = MostPopularClassname(m.Groups["inner"].Value);
													if (!string.IsNullOrEmpty(className))
														textAttrs["class"] = className;
												}

                                                //  it's worthwhile special-casing a single tspan child with no other text (inkscape pattern)

			                            	    var newInnerText = GetNewInnerText(m.Groups["inner"].Value, textAttrs, cssStyleRules, svgClassName, ref tspansRemoved);

                                                //  illustrator generates text elements using transforms and a single nested tspan.
                                                //  this has us ending up returning inner text like <text x="0" y="0">the text</text>
                                                //  here, we detect that and have us return a text instead of multiple a group wrapping multiple texts
												var innerTextMatch = Regex.Match(newInnerText, @"^<text x=""(?<x>[\d\.]+)"" y=""(?<y>[\d\.]+)"">(?<t>[^<]*)</text>$");
			                            	    var wrappingTag = "g";
                                                if (innerTextMatch.Success)
                                                {
                                                    newInnerText = innerTextMatch.Groups["t"].Value;
                                                    wrappingTag = "text";
													textAttrs["x"] = innerTextMatch.Groups["x"].Value;
													textAttrs["y"] = innerTextMatch.Groups["y"].Value;
												}

                                                var openTag = string.Empty;
                                                var closeTag = string.Empty;
                                                if (wrappingTag == "text" || textAttrs.ContainsKey("class") || textAttrs.ContainsKey("transform"))
												{
													openTag = string.Format("<{0}{1}{2}{3}{4}>",
                                                                            wrappingTag,
																			textAttrs.ContainsKey("x") ? string.Format(" x=\"{0}\"", textAttrs["x"]) : string.Empty,
																			textAttrs.ContainsKey("y") ? string.Format(" y=\"{0}\"", textAttrs["y"]) : string.Empty,
																			textAttrs.ContainsKey("class") ? string.Format(" class=\"{0}\"", textAttrs["class"]) : string.Empty,
																			textAttrs.ContainsKey("transform") ? string.Format(" transform=\"{0}\"", textAttrs["transform"]) : string.Empty);
													closeTag = string.Format("</{0}>", wrappingTag);
												}

			                            	    return openTag + newInnerText + closeTag;
			                            	});

			if (tspansRemoved > 0)
				messager("{0:#,##0} tspan elements converted to or merged with text elements.", tspansRemoved);

			return svg;
		}

		public static string CollapseCollabsibleTextRuns(string svg, string cssStyleRules, CleanSVG.StatusMessager messager)
		{
			var runsCollapsed = 0;
			var resultingRuns = 0;
			messager("Collapsing adjacent text elements on the same line...");

			var svgClassMatch = Regex.Match(svg, @"<svg\s[^>]*?class\s*=\s*""(?<classname>[^""]*)""");
			var svgClassName = svgClassMatch.Success ? svgClassMatch.Groups["classname"].Value : string.Empty;

            //  visio files are output with an implied scale from viewBox to width,height
            //  we need to apply that implied scale to our text measurements to get them right
            var svgHeightMatch = Regex.Match(svg, @"<svg\s[^>]*?height\s*=\s*""(?<height>[^""]*)""");
            var svgViewBoxMatch = Regex.Match(svg, @"<svg\s[^>]*?viewBox\s*=\s*""\s*[\d\.]+\s+[\d\.]+\s+[\d\.]+\s+(?<vbh>[\d\.]+)\s*""");
		    var svgScale = svgHeightMatch.Success && svgViewBoxMatch.Success
		                         ? ParseUnits(svgHeightMatch.Groups["height"].Value)/float.Parse(svgViewBoxMatch.Groups["vbh"].Value)
		                         : 1F;

		    var rxCollapsibleTextRuns = new Regex(@"(?<openTag><text(\s+(class\s*=\s*""(?<c>\w+)""|y\s*=\s*""(?<y>[+-]{0,1}\d+(\.\d{0,2})?)\d*""|x\s*=\s*""(?<x>[^""]*)""))*\s*>)(?<t>[^>]*)</text>(\s*<text(\s+(class\s*=\s*""\k<c>""|y\s*=\s*""\k<y>\d*""|x\s*=\s*""(?<x>[^""]*)""))*\s*>(?<t>[^>]*)</text>)+", RegexOptions.Singleline);

			var fonts = new Dictionary<string, Font> { { svgClassName, CreateFontFromClass(svgClassName, cssStyleRules) } };

			using (var g = Program.TheMainForm.CreateGraphics())
			{
				g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
				g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
				svg = rxCollapsibleTextRuns.Replace(svg,
				                                    delegate(Match m)
				                                    	{
                                                            var x = m.Groups["x"].Captures.Count > 0 ? float.Parse(m.Groups["x"].Captures[0].Value) : 0F;
                                                            var newText = m.Groups["t"].Captures.Count > 0 ? m.Groups["t"].Captures[0].Value : string.Empty;
															var textClass = m.Groups["c"].Captures.Count > 0 ?  m.Groups["c"].Captures[0].Value : string.Empty;
															fonts[textClass] = CreateFontFromClass(textClass, cssStyleRules, fonts[svgClassName]);
															var computedX = x + SpanWidth(g, newText, fonts[textClass]) / svgScale;

															for (var i = 1; i < m.Groups["t"].Captures.Count; ++i)
															{
                                                                var thisX = m.Groups["x"].Captures.Count > i ? float.Parse(m.Groups["x"].Captures[i].Value) : computedX;
																var thisText = m.Groups["t"].Captures[i].Value;

																//	bail out if the difference is simply too great
																if (Math.Abs(computedX - thisX) > 10)
																{
																	messager("Rejected collapsing \"{0}\" and \"{1}\"; computedX = {2}; thisX = {3}.", newText, thisText, computedX, thisX);
																	return m.Value;
																}

                                                                //  bail out if this run has a class and it's not the same as the text class
                                                                var thisClass = m.Groups["c"].Captures.Count > i ? m.Groups["c"].Captures[i].Value : string.Empty;
                                                                if (!string.IsNullOrEmpty(thisClass) && thisClass != textClass)
                                                                {
                                                                    messager("Rejected collapsing \"{0}\" and \"{1}\" because classes differ.", newText, thisText);
                                                                    return m.Value;
                                                                }

																if (computedX + 0.1 < thisX)
																	thisText = " " + thisText;

																newText += thisText;
                                                                computedX = thisX + SpanWidth(g, thisText, fonts[textClass]) / svgScale;
															}

				                                    		runsCollapsed += m.Groups["t"].Captures.Count;
				                                    		resultingRuns += 1;
				                                    		return m.Groups["openTag"].Value + newText + "</text>";
				                                    	});
			}

			//	clean up fonts created
			foreach (var font in fonts.Values)
				font.Dispose();

			if (runsCollapsed > 0)
				messager("{0:#,##0} text elements collapsed into {1:#,##0}.", runsCollapsed, resultingRuns);

			return svg;
		}

		private static bool AboutEqual(string stringA, float b)
	    {
	        return AboutEqual(float.Parse(stringA.Trim()), b);
	    }

        private static bool AboutEqual(float a, float b)
        {
            return (Math.Abs(a - b) < 0.01);
        }

	    private static string MostPopularClassname(string innerText)
		{
		    var classNames = new Dictionary<string, int>();
			for (var m = _rxClass.Match(innerText); m.Success; m = m.NextMatch())
			{
				if (!classNames.ContainsKey(m.Groups["classname"].Value))
					classNames.Add(m.Groups["classname"].Value, 0);

				classNames[m.Groups["classname"].Value] += 1;
			}

			if (classNames.Count == 0)
				return null;

			var classList = new List<KeyValuePair<string, int>>(classNames);
			classList.Sort((a, b) => b.Value.CompareTo(a.Value));	// reverse sort by count
			return classList[0].Key;
		}

		private static Dictionary<string, string> GetAtributesDictionary(Match m)
		{
			var textAttrs = new Dictionary<string, string>();

			for (var i = 0; i < m.Groups["attr"].Captures.Count; ++i)
				textAttrs[m.Groups["attr"].Captures[i].Value] = m.Groups["value"].Captures[i].Value;
			return textAttrs;
		}

		private static string GetNewInnerText(string innerText, IDictionary<string, string> textAttrs, string cssStyleRules, string svgClass, ref int tspansRemoved)
		{
            // ReSharper disable AssignNullToNotNullAttribute
            var newInner = new StringBuilder();
			var fonts = new Dictionary<string, Font> {{svgClass, CreateFontFromClass(svgClass, cssStyleRules)}};

			using (var g = Program.TheMainForm.CreateGraphics())
			{
				g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
				g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

				float translateX, translateY;
				GetTranslateXYFromTransform(textAttrs, out translateX, out translateY);

			    //	save some attributes from the parent text element
				var currentX = translateX + (textAttrs.ContainsKey("x") ? float.Parse(textAttrs["x"]) : 0F);
				var currentY = translateY + (textAttrs.ContainsKey("y") ? float.Parse(textAttrs["y"]) : 0F);
				var textClass = textAttrs.ContainsKey("class") ? textAttrs["class"] : string.Empty;
				fonts[textClass] = CreateFontFromClass(textClass, cssStyleRules, fonts[svgClass]);

				var currentIndex = 0;
				var pendingText = string.Empty;

				for (var tsm = _rxTspanElement.Match(innerText); tsm.Success; tsm = tsm.NextMatch())
				{
					++tspansRemoved;

					var tspanAttrs = GetAtributesDictionary(tsm);

					var tspanClass = tspanAttrs.ContainsKey("class") ? tspanAttrs["class"] : textClass;

					if (!fonts.ContainsKey(tspanClass))
						fonts[tspanClass] = CreateFontFromClass(tspanClass, cssStyleRules, fonts[textClass]);

					var tspanX = currentX;
					if (tspanAttrs.ContainsKey("x"))
						tspanX = translateX + float.Parse(tspanAttrs["x"].Split(' ', ',')[0]);

					var tspanY = currentY;
					if (tspanAttrs.ContainsKey("y"))
                        tspanY = translateY + float.Parse(tspanAttrs["y"].Split(' ', ',')[0]);
					else if (tspanAttrs.ContainsKey("dy"))
						tspanY += ParseUnits(tspanAttrs["dy"], fonts[tspanClass].Size);

					//	append content outside of tspan elements
					if (currentIndex < tsm.Index)
						pendingText += TrimSVGText(innerText.Substring(currentIndex, tsm.Index - currentIndex));

					var tspanInner = TrimSVGText(tsm.Groups["inner"].Value);

					//	only create a new text run if there's a change in Y or css class. ignore changes in x alone.
					if (AboutEqual(tspanY, currentY) && (textClass == string.Empty || tspanClass == textClass))
					{
						pendingText += tspanInner;
					}
					else
					{
						if (!string.IsNullOrEmpty(pendingText) && !Regex.IsMatch(pendingText, @"^\s*$"))
						{
							newInner.AppendFormat("<text x=\"{0}\" y=\"{1}\"{3}>{2}</text>", currentX, currentY, pendingText,
                                !string.IsNullOrEmpty(textClass) ? string.Format(" class=\"{0}\"", textClass) : string.Empty);

							if (!tspanAttrs.ContainsKey("x"))
								tspanX += SpanWidth(g, pendingText, fonts[textClass]);
						}

						pendingText = string.Empty;

						newInner.AppendFormat("<text x=\"{0}\" y=\"{1}\"", tspanX, tspanY);

						if (!string.IsNullOrEmpty(tspanClass))
							newInner.AppendFormat(" class=\"{0}\"", tspanClass);

						newInner.Append(">").Append(tspanInner).Append("</text>");
						currentX = tspanX + SpanWidth(g, tspanInner, fonts[tspanClass]);
						currentY = tspanY;
					}

					currentIndex = tsm.Index + tsm.Length;
				}

				if (currentIndex < innerText.Length)
					pendingText += TrimSVGText(innerText.Substring(currentIndex));

				if (!string.IsNullOrEmpty(pendingText) && !Regex.IsMatch(pendingText, @"^\s*$"))
                    newInner.AppendFormat("<text x=\"{0}\" y=\"{1}\"{3}>{2}</text>", currentX, currentY, pendingText,
                        !string.IsNullOrEmpty(textClass) ? string.Format(" class=\"{0}\"", textClass) : string.Empty);
            }

			//	clean up fonts created
			foreach (var font in fonts.Values)
				font.Dispose();

			return newInner.ToString();
            // ReSharper restore AssignNullToNotNullAttribute
        }

	    private static bool GetTranslateXYFromTransform(IDictionary<string, string> textAttrs, out float translateX, out float translateY)
		{
			translateX = 0F;
			translateY = 0F;

			if (textAttrs.ContainsKey("transform"))
			{
				var rxMatrix = new Regex(@"matrix\((?<array>\s*[+-]{0,1}\d+(?:\.\d*)?){6}\s*\)");
				var matrixMatch = rxMatrix.Match(textAttrs["transform"]);
				if (matrixMatch.Success
				    && AboutEqual(matrixMatch.Groups["array"].Captures[0].Value, 1)
				    && AboutEqual(matrixMatch.Groups["array"].Captures[1].Value, 0)
				    && AboutEqual(matrixMatch.Groups["array"].Captures[2].Value, 0)
				    && AboutEqual(matrixMatch.Groups["array"].Captures[3].Value, 1))
				{
					textAttrs.Remove("transform");
					translateX = float.Parse(matrixMatch.Groups["array"].Captures[4].Value.Trim());
					translateY = float.Parse(matrixMatch.Groups["array"].Captures[5].Value.Trim());
					return true;
				}
			}

			return false;
		}

		private static float SpanWidth(Graphics g, string pendingText, Font font)
		{
            return g.MeasureString("0" + pendingText.Replace("&amp;", "&").Replace("&lt;", "<").Replace("&gt;", ">") + "0", font, 0, StringFormat.GenericTypographic).Width
				- g.MeasureString("00", font, 0, StringFormat.GenericTypographic).Width;
		}

		//	this is trying to implement something like correct whitespace handling.
		//	in particular, if the entire string is whitespace, we return the empty string.
		//	however, if the entire string isn't whitespace, we convert any leading spaces to unicode non-breaking spaces.
		private static string TrimSVGText(string text)
		{
			text = Regex.Replace(text, @"^ +", m => m.Value.Replace(' ', '\u00A0'));
			text = Regex.Replace(text, @" +$", m => m.Value.Replace(' ', '\u00A0'));
			return text;
		}

		private static Font CreateFontFromClass(string className, string cssStyleRules)
		{
			Font createdFont;

			using (var svgFont = new Font("Arial", 12, FontStyle.Regular))
				createdFont = CreateFontFromClass(className, cssStyleRules, svgFont);

			return createdFont;
		}

		private static Font CreateFontFromClass(string className, string cssStyleRules, Font parentFont)
		{
			var fontFamily = parentFont.FontFamily.Name;
			var fontSize = parentFont.Size;
			var fontStyle = parentFont.Style;

			if (!string.IsNullOrEmpty(className))
			{
				var rxFontProps = new Regex(@"\." + className + @"\s*\{(?<props>[^}]*?(?<key>[\w\-]*):\s*(?<value>[^;}]*))*[^}]*}", RegexOptions.Compiled);

				var m = rxFontProps.Match(cssStyleRules);
				if (m.Success)
				{
					var allProps = new Dictionary<string, string>();

					for (var i = 0; i < m.Groups["key"].Captures.Count; ++i)
						allProps.Add(m.Groups["key"].Captures[i].Value, m.Groups["value"].Captures[i].Value);

					if (allProps.ContainsKey("font-family")) fontFamily = allProps["font-family"].Trim(' ', '\'', '"');
					if (allProps.ContainsKey("font-size")) fontSize = ParseUnits(allProps["font-size"], fontSize);
					if (allProps.ContainsKey("font-weight") && allProps["font-weight"] == "bold") fontStyle |= FontStyle.Bold;
					if (allProps.ContainsKey("font-style") && allProps["font-style"] == "italic") fontStyle |= FontStyle.Italic;
				}
			}

			return new Font(fontFamily, fontSize, fontStyle, GraphicsUnit.Pixel);
		}

		//	returns the value in pixels
		public static float ParseUnits(string valueString)
		{
			return ParseUnits(valueString, 12f);
		}

		public static float ParseUnits(string valueString, float baseFontSize)
		{
			var floatValue = float.Parse(Regex.Match(valueString, @"^(\+|-)?\d*(\.\d*)?").Value);
			var units = Regex.Match(valueString, @"(pt|px|em|in|cm)?$").Value;

			switch (units)
			{
				case "in":
					floatValue = floatValue * 96F;
					break;
				case "cm":
					floatValue = floatValue * 96F / 2.54F;
					break;
				case "pt":
					floatValue = floatValue * 96F / 72F;
					break;
				case "em":
					floatValue = floatValue * baseFontSize;
					break;
			}

			return floatValue;
		}

	}
}
